/* 
 * Ardesia -- a program for painting on the screen
 * with this program you can play, draw, learn and teach
 * This program has been written such as a freedom sonet
 * We believe in the freedom and in the freedom of education
 *
 * Copyright (C) 2009 Pilolli Pietro <pilolli@fbk.eu>
 *
 * Ardesia is free software: you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Ardesia is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along
 * with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */


#include <utils.h>

#ifdef HAVE_BACKTRACE

#include <glibc_backtrace.h>
#include <crash_dialog.h>

#ifdef HAVE_LIBSIGSEGV
#  include <sigsegv.h>
#endif

#ifdef HAVE_LIBBFD
#  include <bfd.h>
#  if ( defined (__macos_x__) || defined (__macos_x) || defined (_macos_x) || defined (macos_x) || \
	defined (__apple__) || defined (__apple) || defined (_apple) || defined (apple) )
#  include <sys/param.h>
#  include <mach-o/dyld.h>
#endif


/* Globals retained across calls to resolve. */
static bfd *abfd = (bfd *) NULL;
static asymbol **syms = (asymbol ** ) NULL;
static asection *text = (asection *) NULL;


/* Put a trace line in the file giving the address. */
static void
create_trace_line (char *address,
		   FILE *file)
{
  char ename[1024];
  ssize_t l = -1;
  long storage_needed = 0;
  unsigned long offset = 0;
#if ( defined (__freebsd__) || defined (__freebsd) || defined (_freebsd) || defined (freebsd) )
  l = readlink ("/proc/curproc/file", chrarray_Buffer, PATH_MAXLEN);
#elif ( defined (__macos_x__) || defined (__macos_x) || defined (_macos_x) || defined (macos_x) || \
	defined (__apple__) || defined (__apple) || defined (_apple) || defined (apple) )
  u32 u32_buffer_length ;
  if ( _NSGetExecutablePath ( chrarray_Buffer, &u32_buffer_length ))
    {
      printf ("An error occured while reading the executable path. Program terminated.\n");
    }
#else // Linux
  l = readlink ("/proc/self/exe",ename,sizeof (ename));
#endif

  if (l == -1)
    {
      fprintf (stderr, "failed to find executable\n");
      return;
    }
	
  ename[l] = 0;

  bfd_init ();

  abfd = bfd_openr (ename, 0);

  if (!abfd)
    {
      fprintf (stderr, "bfd_openr failed: ");
      return;
    }

  /* Oddly, this is required for it to work... */
  if (!bfd_check_format (abfd, bfd_object))
    {
      fprintf (stderr, "bfd_check_format failed\n");
      return;
    }

  storage_needed = bfd_get_symtab_upper_bound (abfd);
  syms = (asymbol **) malloc (storage_needed);

  text = bfd_get_section_by_name (abfd, ".text");

  offset = ( (unsigned long) address) - text->vma;

  if (offset > 0)
    {
      const char *filen;
      const char *func;
      unsigned line;
      if (bfd_find_nearest_line (abfd, text, syms, offset, &filen, &func, &line) && file)
	{
	  fprintf (stderr, "file: %s, line: %u, func %s\n", filen, line, func);
	  fprintf (file, "file: %s, line: %u, func %s\n", filen, line, func);
	}
    }
}
#else
static void
create_trace_line (char *address,
		   FILE *file)
{
  fprintf (stderr, "Unable to create the stacktrace line; check the bfd library installation\n");
}
#endif


#ifdef HAVE_BACKTRACE

/* Create the trace to be printed. */
static void
create_trace ()
{

  void  *array[MAX_FRAMES];
  void  *approx_text_end = (void *) ( (128+100) * 2<<20);
  gchar *default_filename = get_default_filename ();
  gchar *backtrace_name = g_strdup_printf ("%s_stacktrace.txt", default_filename);
  gchar *filename  = g_build_filename ( g_get_tmp_dir (), backtrace_name, (gchar *) 0);
  FILE *file = fopen (filename, "w");
  size_t size;
  size_t i;

  g_free (default_filename);
  g_free (backtrace_name);


  /*
   * The glibc functions backtrace is missing on all non-glibc platforms.
   */

  size = backtrace (array, MAX_FRAMES);
  g_free (default_filename);

  for (i = 0; i < size; i++)
    {
      if (array[i] < approx_text_end)
	{
	  create_trace_line (array[i], file);
	}
    }

  fclose (file);
  start_crash_dialog (NULL, filename);
  g_free (filename);
}
#else
static void
create_trace ()
{
  fprintf (stderr, "Unable to create the stack-trace the glibc back-trace function is not supported by your system\n");
  fprintf (stderr, "Please use the gdb and the bt command to create the trace\n");
}
#endif


/* Is called when occurs a sigsegv. */
static int
sigsegv_handler (void *addr,
		 int bad)
{
  create_trace ();
  exit (EXIT_FAILURE);
}


/* Register the back-trace handler. */
void
glibc_backtrace_register ()
{
#ifdef HAVE_LIBSIGSEGV
  g_type_init();
  /* Install the SIGSEGV handler. */
  if (sigsegv_install_handler (sigsegv_handler)<0)
    {
      exit (EXIT_FAILURE);
    }

#endif
}

#endif


